#! /usr/bin/python2.4
#
#
# jhebert@cs.washington.edu

import time
import BaseHTTPServer
import SocketServer
import urllib2
import httplib
import socket
import Queue

#TODO: make status messages more clear.

class WebServer(SocketServer.ThreadingMixIn,
                SocketServer.TCPServer,BaseHTTPServer.HTTPServer):
  """ This webserver holds the event data and gives it out
  in various different ways. """
  def Init(self):
    self.savedLog, self.nodeLog, self.timeLog = {}, {}, {}
    self.infoLog = {}
    self.tryingToExit=False
    
  def StartServing(self):
    self.tryingToExit=False
    while 1:
      if(self.tryingToExit):
        time.sleep(.5)
        self.server_close()
        break
      self.handle_request()

  def PleaseShutDown(self):
    """ Attempts to shut down the server which is still listening
    for a single connection from the StartServing method. So it
    connects to itself just once before closing. """
    self.tryingToExit=True
    port=self.server_address[1]
    url=''.join(['http://127.0.0.1:',str(port),'/index.html'])
    print 'Shutting down server on port', port
    try:
      urllib2.urlopen(url)
    except httplib.BadStatusLine:
      pass
    except urllib2.HTTPError:
      pass
    except urllib2.URLError:
      pass

  def SendHeaders(self, info, handler):
    handler.send_response(200, "OK")
    handler.send_header('Content-type', 'text; charset="utf-8"')
    #handler.send_header('Content-Length', str(len(dataToSend)))
    handler.end_headers()
    header=(
      '<html>\n'
      '<title>%(title)s</title>\n'
      '<body><a href="index.html" style="text-decoration:none">'
      '<h2>%(title)s</h2></a><hr>\n') % ({
      'title':'ChartRunner Status Page - '+info})
    handler.wfile.write(header)

  def SendPageToUser(self, dataToSend, requestHandler):
    """ Centralized method to return content to the user. """
    requestHandler.wfile.write(dataToSend)
    requestHandler.wfile.flush()

  def SendErrorToUser(self, requestHandler):
    """ Centralized method to return errors to the user. """
    requestHandler.send_error(404, "Denied")
    requestHandler.end_headers()

  def AssembleIntroLogPage(self):
    toReturn=['<table border>']
    for item in self.savedLog:
      toAdd=('<tr><td>%(chart)s</td>\n'
             '<td>%(count)s</td></tr>\n' ) % ({
        'chart': self.__MakeLink(item, item, 'chart'),
        'count' : str(len(self.savedLog[item])), })
      toReturn.append(toAdd)
    toReturn.append('</table></html>\n')
    return ''.join(toReturn)

  def AssembleChartLogPage(self, path):
    toReturn=[]
    for chart in self.savedLog:
      if(path.find(self.__MakeHash(chart))==-1):
        continue
      for data in self.savedLog[chart]:
        timeStr,node,b,c=data
        t = self.__ConvertTime(timeStr)
        toAdd=(
          '<tr><td>%(time)s</td>\n'
          '<td><code>%(name)s</code></td>\n'
          '<td>%(info)s</td>\n'
          '<td><pre>%(end)s</pre></td></tr>\n') % ({
          'time':self.__MakeLink((chart,timeStr),t,'time'),
          'name':self.__MakeLink((chart,node), node, 'node'),
          'info':b,
          'end':c, })
        toReturn.append(toAdd)
      toReturn.append('<table border><br><br>')
      if(chart in self.infoLog):
        toReturn.append(self.infoLog[chart][-1])
      toReturn.append('<h3>'+chart+'</h3>')
      toReturn.reverse()
    toReturn.append('</table></html>\n')
    return ''.join(toReturn)

  def AssembleNodeLogPage(self, path):
    toReturn=[]
    for item in self.nodeLog:
      if(path.find(self.__MakeHash(item))==-1):
        continue
      chart, node = item
      for data in self.nodeLog[item]:
        t,b,c=data
        toAdd=(
          '<tr><td>%(time)s</td>\n'
          '<td>%(info)s</td>\n'
          '<td><pre>%(end)s</pre></td></tr>\n')%({
          'time':self.__MakeLink((chart,time), time, 'time'),
          'info' : b,
          'end' : c, })
        toReturn.append(toAdd)
      toReturn.append('<table border><br><br>')
      if(item[1] in self.infoLog):
        toReturn.append(self.infoLog[item[1]][-1])
      toReturn.append('<h3>'+item[1]+'</h3>')
      toReturn.reverse()
    toReturn.append('</table></html>\n')
    return ''.join(toReturn)

  def AssembleTimeLogPage(self, path):
    toReturn=[]
    for item in self.timeLog:
      if(path.find(self.__MakeHash(item))==-1):
        continue
      chart, timeStr = item
      for data in self.timeLog[item]:
        node,b,c=data
        toAdd=(
          '<tr><td>%(node)s</td>\n'
          '<td>%(info)s</td>\n'
          '<td><pre>%(end)s</pre></td></tr>\n')%({
          'node':self.__MakeLink((chart,node), node, 'node'),
          'info':b,
          'end':c, })
        toReturn.append(toAdd)
      toReturn.append('<table border><br><br>')
      toReturn.reverse()
    toReturn.append('</table></html>\n')
    return ''.join(toReturn)

  def UpdateLogFromItem(self, item):
    """ Update internal data structures with the new event.
    Chart,time,name,message,object  """
    try:
      chart,t,a,b,c=[str(a) for a in item]
      c=c.replace("\n","<br>\n")
      if(b=='info'):
        self.__SaveItemInHash(self.infoLog, a, c)
      c=self.__EscapeAngleBrackets(c)
      self.__SaveItemInHash(self.savedLog, chart, (t,a,b,c))
      self.__SaveItemInHash(self.nodeLog, (chart,a), (t,b,c))
      self.__SaveItemInHash(self.timeLog, (chart,t), (a,b,c))
    except Queue.Empty, err:
      pass

  def __MakeLink(self, item, name, typeName):
    toReturn = ('<a href="./%(hashItem)s-%(type)s">%(name)s</a>') % ({
      'name' : name,
      'type' : typeName,
      'hashItem' : self.__MakeHash(item), })
    return toReturn

  def __MakeHash(self, item):
    num=hash(item)
    if(num<0):
      num=-num
    return str(num)

  def __EscapeAngleBrackets(self, string):
    toReturn=string.replace('\n','<br>\n')
    toReturn=toReturn.replace('>', '&gt;')
    toReturn=toReturn.replace('<', '&lt;')
    return toReturn

  def __SaveItemInHash(self, hashTable, key, value):
    if(key in hashTable):
      hashTable[key].append(value)
    else:
      hashTable[key]=[value]

  def __ConvertTime(self, numTime):
    """ Convert a unix time to hour:minute:second. """
    strT = time.localtime(float(numTime))
    return time.strftime('%H:%M:%S', strT)
    

class QueryListener(BaseHTTPServer.BaseHTTPRequestHandler):
  """ Request Handler for StatusSever. """
  
  def do_GET(self):
    """ Handle requests as long as we are not supposed to be exiting. """
    self.server.SendHeaders(self.path, self)
    if((self.path.find('index.html')>-1)|(len(self.path)<3)):
      data=self.server.AssembleIntroLogPage()
    elif(self.path.find('chart')>-1):
      data=self.server.AssembleChartLogPage(self.path)
    elif(self.path.find('node')>-1):
      data=self.server.AssembleNodeLogPage(self.path)
    elif(self.path.find('time')>-1):
      data=self.server.AssembleTimeLogPage(self.path)
    else:
      data='<html>Meh.</html>'
    self.server.SendPageToUser(data, self)



